package top.lrshuai.websocket.ws.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import top.lrshuai.websocket.ws.handler.DataChannelHandler;
import top.lrshuai.websocket.ws.handler.HeartBeatChannelHandler;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class NettyServer {

    @Autowired
    private DataChannelHandler dataChannelHandler;

    private static final String THREAD_NAME_PREFIX="nettyStart-";
    @Value("${ws.port}")
    private int port;
    @Value("${ws.webSocketPath}")
    private String webSocketPath;

    /**
     * readerIdleTime秒未接收到消息触发,0表示不启用
     */
    @Value("${ws.netty.readerIdleTime}")
    private int readerIdleTime;

    /**
     * writerIdleTime秒未写消息到客户端触发,0表示不启用
     */
    @Value("${ws.netty.writerIdleTime}")
    private int writerIdleTime;

    /**
     * allIdleTime秒未写消息到客户端，并且未收取到触发,0表示不启用
     */
    @Value("${ws.netty.allIdleTime}")
    private int allIdleTime;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;


    /**
     * 启动
     *
     * @throws InterruptedException
     */
    private void start() throws InterruptedException {
        this.bossGroup= new NioEventLoopGroup();
        this.workGroup= new NioEventLoopGroup(16);
        ServerBootstrap bootstrap = new ServerBootstrap();
        // bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作
        bootstrap.group(this.bossGroup, this.workGroup);
        // 设置NIO类型的channel,非阻塞模式
        bootstrap.channel(NioServerSocketChannel.class);
        // 设置监听端口
        bootstrap.localAddress(new InetSocketAddress(this.port));
        //链接缓冲池队列大小
        bootstrap.option(ChannelOption.SO_BACKLOG, 2048);
        bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
        // 连接到达时会创建一个通道
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel ch)  {
                //心跳检测，触发 userEventTriggered 事件
                ch.pipeline().addLast(new IdleStateHandler(readerIdleTime, writerIdleTime, allIdleTime, TimeUnit.SECONDS));
                //将字节解码为HttpRequest、HttpContent 和LastHttpContent。并将HttpRequest、HttpContent 和LastHttpContent 编码为字节
                ch.pipeline().addLast(new HttpServerCodec());

                //http数据在传输过程中是分段的，HttpObjectAggregator可以将多个段聚合
                //装了这个之后， ChannelPipeline 中的下一个ChannelHandler 将只会收到完整的HTTP 请求或响应
                ch.pipeline().addLast(new HttpObjectAggregator(65536));
                // 以块的方式来写的处理器
                ch.pipeline().addLast(new ChunkedWriteHandler());
                // WebSocketServerProtocolHandler 对websocket做支持， 按照WebSocket 规范的要求，处理WebSocket 升级握手、PingWebSocketFrame 、PongWebSocketFrame 和CloseWebSocketFrame
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath));
                ch.pipeline().addLast(new HeartBeatChannelHandler());
                // 自定义的handler，处理业务逻辑
                ch.pipeline().addLast(dataChannelHandler);

            }
        });

//        log.info("启动netty成功...");
        log.info("netty服务器在[{}]端口启动监听", this.port);
        // 配置完成，开始绑定server，通过调用sync同步方法阻塞直到绑定成功
        ChannelFuture channelFuture = bootstrap.bind().sync();
        // 对关闭通道进行监听
        channelFuture.channel().closeFuture().sync();
    }

    @PreDestroy
    public void destroy() throws InterruptedException {
        if (this.bossGroup != null) {
            this.bossGroup.shutdownGracefully().sync();
        }
        if (this.workGroup != null) {
            this.workGroup.shutdownGracefully().sync();
        }
    }

    @PostConstruct
    public void init() {
        //需要开启一个新的线程来执行netty server 服务器
        Thread initThread = new Thread(() -> {
            try {
                start();

            } catch (Exception e) {
                e.printStackTrace();
            }
        });
//        initThread.setName(THREAD_NAME_PREFIX+ WebSocketEnum.DATA.name());
        initThread.setName(THREAD_NAME_PREFIX);
        initThread.start();
    }

}
